﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace DLR02
{
    /// <summary>
    /// Create the architecture necessary to make a recursive dynamic method,
    /// in this case a function which returns a Fibonacci number.
    /// </summary>
    /// <remarks>
    /// The easiest way to accomplish recursive function calls, is to first create
    /// a parameter that will store the lamba expression, then create the lambda
    /// expression representing your function, then assign you parameter to
    /// your lambda expression.  Use the Expression.Invoke() method to call your
    /// parameter from within your lambda expression, and the parameter doesn't
    /// need to be set when the lambda expression is defined.
    /// </remarks>
    public class Program
    {
        public static void Main(string[] args)
        {
            var genFiboFunc = GenerateFibo();
            
            for (int n = 0; n < 20; n++)
            {
                int fibo = Fibo(n);
                int genFibo = genFiboFunc(n);
                Console.WriteLine("{0}: {1}, {2}", (fibo == genFibo) ? "Pass" : "Fail", fibo, genFibo);
            }
        }

        public static Func<int, int> GenerateFibo()
        {
            var fibo = Expression.Parameter(typeof(Func<int, int>), "fibo");
            var n = Expression.Parameter(typeof(int), "n");

            var ret = Expression.Label(typeof(int), "return");

            var lambda =
                Expression.Lambda<Func<int, int>>(
                    Expression.Block(
                        Expression.IfThen(
                            Expression.GreaterThan(n, Expression.Constant(1)),
                            Expression.Return(
                                ret,
                                Expression.Add(
                                    Expression.Invoke(fibo, Expression.Subtract(n, Expression.Constant(1))),
                                    Expression.Invoke(fibo, Expression.Subtract(n, Expression.Constant(2)))))),
                        Expression.Label(ret, Expression.Constant(1))),
                    n);

            return
                Expression.Lambda<Func<int, int>>(
                    Expression.Block(
                        new[] { fibo },
                        Expression.Assign(fibo, lambda),
                        Expression.Invoke(fibo, n)),
                    n).Compile();
        }

        public static int Fibo(int n)
        {
            if (n <= 1)
            {
                return 1;
            }
            return Fibo(n - 1) + Fibo(n - 2);
        }
    }
}